{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "ExecuteTime": {
     "start_time": "2020-11-22T04:06:22.891Z"
    }
   },
   "source": [
    "![avatar]()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0. 导包，定义数据结构"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-11-22T03:07:41.185689Z",
     "start_time": "2020-11-22T03:07:40.864671Z"
    }
   },
   "outputs": [],
   "source": [
    "#导入包\n",
    "from pulp import *\n",
    "\n",
    "#六种原材料\n",
    "items = ['chicken', 'beef', 'mutton', 'rice', 'wheat', 'gel']\n",
    "\n",
    "#每种原材料的成本\n",
    "costs = {'chicken': 0.013, \n",
    "         'beef': 0.008, \n",
    "         'mutton': 0.010, \n",
    "         'rice': 0.002, \n",
    "         'wheat': 0.005, \n",
    "         'gel': 0.001}\n",
    "\n",
    "#六种原材料所含的protein的数量\n",
    "protein = {'chicken': 0.100, \n",
    "            'beef': 0.200, \n",
    "            'mutton': 0.150, \n",
    "            'rice': 0.000, \n",
    "            'wheat': 0.040, \n",
    "            'gel': 0.000}\n",
    "\n",
    "#六种原材料所含的fat的数量\n",
    "fat = {'chicken': 0.080, \n",
    "        'beef': 0.100, \n",
    "        'mutton': 0.110, \n",
    "        'rice': 0.010, \n",
    "        'wheat': 0.010, \n",
    "        'gel': 0.000}\n",
    "\n",
    "#六种原材料所含的fibre的数量\n",
    "fibre = {'chicken': 0.001, \n",
    "          'beef': 0.005, \n",
    "          'mutton': 0.003, \n",
    "          'rice': 0.100, \n",
    "          'wheat': 0.150, \n",
    "          'gel': 0.000}\n",
    "\n",
    "#六种原材料所含的salt的数量\n",
    "salt = {'chicken': 0.002, \n",
    "         'beef': 0.005, \n",
    "         'mutton': 0.007, \n",
    "         'rice': 0.002, \n",
    "         'wheat': 0.008, \n",
    "         'gel': 0.000}\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.定义线性规划的问题，求最小极值  \n",
    "Prob = LpProblem ( \"problem_name\" , sense )  \n",
    "定义Prob变量，用来定义一个LP问题实例，其中problem_name指定问题名（输出信息用），sense值是LpMinimize或LpMaximize中的一个，用来指定目标函数是求最大值还是最小值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-11-22T03:07:41.212691Z",
     "start_time": "2020-11-22T03:07:41.191689Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\ProgramData\\Anaconda3\\lib\\site-packages\\pulp\\pulp.py:1198: UserWarning: Spaces are not permitted in the name. Converted to '_'\n",
      "  warnings.warn(\"Spaces are not permitted in the name. Converted to '_'\")\n"
     ]
    }
   ],
   "source": [
    "#求最小值\n",
    "prob = LpProblem(\"The CatFood Problem\", LpMinimize)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 定义决策变量  \n",
    "有两种方式， \n",
    " - 一种是定义单个变量(适用于变量个数不多的情况）  \n",
    "DV = LpVariable ( decision variable name , lowbound , upbound ,category )  \n",
    "decision variable name指定变量名，lowBound和upBound是下界和上界， 默认是none, 分别代表负无穷和正无穷, category用来指定变量是离散  (LpInteger,LpBinary)还是连续(LpContinuous),\n",
    "\n",
    " - 还有一种方式是用dict方式来定义大量的变量，如下：  \n",
    "Ingredients = ['Chicken','Beef','Mutton','Rice','Wheat','Gel']  \n",
    "variables = LpVariable.dicts (\"Ingr\",Ingredients,0)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-11-22T03:07:41.230692Z",
     "start_time": "2020-11-22T03:07:41.219691Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'chicken': item_chicken, 'beef': item_beef, 'mutton': item_mutton, 'rice': item_rice, 'wheat': item_wheat, 'gel': item_gel}\n"
     ]
    }
   ],
   "source": [
    "#构建Lp变量字典，变量名以item开头，如item_chicken，下界是0\n",
    "item_vars = LpVariable.dicts(\"item\", items, lowBound=0, upBound=100)\n",
    "print(item_vars)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-11-21T14:59:38.750458Z",
     "start_time": "2020-11-21T14:59:38.740457Z"
    }
   },
   "source": [
    "3. 添加目标函数和约束条件  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-11-22T03:07:41.266694Z",
     "start_time": "2020-11-22T03:07:41.237692Z"
    }
   },
   "outputs": [],
   "source": [
    "#添加目标方程，求解的是猫粮的成本，sum(每种原材料的数量*成本)， 每种原材料的数量是上面定义的变量\n",
    "prob += lpSum([costs[i]*item_vars[i] for i in items])\n",
    "\n",
    "#添加约束条件\n",
    "#每100g成品必须至少有8gProtein，6gfat，但是不超过2g的fibre以及0.4g的salt\n",
    "prob += lpSum([item_vars[i] for i in items]) == 100\n",
    "prob += lpSum([protein[i] * item_vars[i] for i in items]) >= 8.0\n",
    "prob += lpSum([fat[i] * item_vars[i] for i in items]) >= 6.0\n",
    "prob += lpSum([fibre[i] * item_vars[i] for i in items]) <= 2.0\n",
    "prob += lpSum([salt[i] * item_vars[i] for i in items]) <= 0.4\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. 求解\n",
    "\n",
    "Prob.slove ( )  \n",
    "输出的结果是’Optimal’，说明找到了最优解。my_lp_problem.status是一个常量，pulp.LpStatus是一个dict，把常量变成可读的字符串：\n",
    "\n",
    "LpStatus = {  \n",
    "    LpStatusNotSolved:\"Not Solved\",  \n",
    "    LpStatusOptimal:\"Optimal\",  \n",
    "    LpStatusInfeasible:\"Infeasible\",  \n",
    "    LpStatusUnbounded:\"Unbounded\",  \n",
    "    LpStatusUndefined:\"Undefined\",  \n",
    "    }    \n",
    "    \n",
    "这些常量的含义是：\n",
    "\n",
    "Not Solved：还没有调研solve()函数前的状态。  \n",
    "Optimal：找到了最优解。  \n",
    "Infeasible：问题没有可行解(比如定义了constraints x <= 1并且x >=2这样的约束)。  \n",
    "Unbounded：约束条件是无界的(not bounded)，最大化会导致无穷大(比如只有一个x >= 3这样的约束)。  \n",
    "Undefined：最优解可能存在但是没有求解出来。  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-11-22T03:07:41.427703Z",
     "start_time": "2020-11-22T03:07:41.283695Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Status: Optimal\n"
     ]
    }
   ],
   "source": [
    "#求解\n",
    "prob.solve()\n",
    "# 查看解的状态, “Not Solved”, “Infeasible”, “Unbounded”, “Undefined” or “Optimal”\n",
    "print(\"Status:\", LpStatus[prob.status])\n",
    "#print('prob.status:', prob.status)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-11-21T15:06:27.489031Z",
     "start_time": "2020-11-21T15:06:27.480030Z"
    }
   },
   "source": [
    "5. 查看结果\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-11-22T03:07:41.444704Z",
     "start_time": "2020-11-22T03:07:41.435703Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "item_chicken = 0.0\n",
      "item_beef = 60.0\n",
      "item_mutton = 0.0\n",
      "item_rice = 0.0\n",
      "item_wheat = 0.0\n",
      "item_gel = 40.0\n"
     ]
    }
   ],
   "source": [
    "# 查看解\n",
    "for i in items:\n",
    "  print('{} = {}'.format(item_vars[i],item_vars[i].value()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "6. 总结  \n",
    "思考程序本质：\n",
    "  problem对象是如何通过不断加来获得目标函数和约束的？熟悉python或者c++的朋友可能会想到一个词：操作符重载。  \n",
    "  没错，就是这么实现的，上述的对象几乎都实现了不同的重载。  \n",
    "  首先是Problem对象prob，全名pulp.pulp.LpProblem；当打印输出（print）时，会打印出问题名，当不断增加目标函数、约束时，也会随着print输出；而它的__add__一定是被定义过了，我们先说其他对象。 \n",
    "  当我们定义一个变量时，它的类型是pulp.pulp.LpVariable，当我们对这些变量和其他变量做加法、和其他常数做乘法时，它会返回一个新的对象，  经检测，这个新对象的类名叫pulp.pulp.LpAffineExpression，顾名思义，叫做关系表达式；如果print，会打印这个关系表达式。  \n",
    "  而如果对关系表达式做出：>=、<=、==时，会返回新的对象，类名叫做pulp.pulp.LpConstraint，即约束对象；如果print，会打印这个约束。  \n",
    "  将关系表达式（pulp.pulp.LpAffineExpression）与问题（pulp.pulp.LpProblem）相加时，会返回新的问题对象，并且新的问题对象会将这个关系表达式作为目标函数。  \n",
    "  将约束对象（pulp.pulp.LpConstraint）与问题（pulp.pulp.LpProblem）相加时，会返回新的问题对象，并且这个新的问题对象会多出约束对象所代表的约束条件。  \n",
    "  调用问题对象的solve方法，解出线性规划的解。  \n",
    "  访问问题对象的objective成员变量，会得到目标函数（关系表达式对象）。  \n",
    "  调用pulp的value方法，将获得对变量代入值的结果，如果是关系表达式对象，将获得优化结果；如果是变量对象，将获得优化结果达到时的变量取值；如果是None，说明你忘调用solve了。  "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
